-- openwisp uci utils
local io = require('io')
local lfs = require('lfs')

local utils = {}

function utils.starts_with_dot(str)
    return str:sub(1, 1) == '.'
end

function utils.split(input, sep)
    if input == '' or input == nil then
        return {}
    end
    if sep == nil then
        sep = '%s'
    end
    local t={}; local i=1
    for str in string.gmatch(input, '([^' .. sep .. ']+)') do
        t[i] = str
        i = i + 1
    end
    return t
end

function utils.basename(path)
    local parts = utils.split(path, '/')
    return parts[table.getn(parts)]
end

function utils.dirname(path)
    local parts = utils.split(path, '/')
    local returnPath = '/'
    local length = table.getn(parts)
    for i, part in ipairs(parts) do
        if i < length then
            returnPath = returnPath..part..'/'
        end
    end
    return returnPath
end

function utils.add_values_to_set(set, values)
    for _, el in pairs(values) do
        set[el] = true
    end
    return set
end

-- writes uci section, eg:
--
-- utils.write_uci_section(cursor, 'network', {
--     [".name"] = "wan",
--     [".type"] = "interface",
--     [".anonymous"] = false,
--     proto = "none",
--     ifname = "eth0.2",
-- }
--
-- will write to "network" the following:
--
--     config interface 'wan'
--         option proto 'none'
--         option ifname 'eth0.2'
--
function utils.write_uci_section(cursor, config, section)
    local name
    -- add named section
    if not section['.anonymous'] then
        name = section['.name']
        cursor:set(config, name, section['.type'])
    -- add anonymous section
    else
        name = cursor:add(config, section['.type'])
    end
    -- write options for section
    for key, value in utils.sorted_pairs(section) do
        utils.write_uci_option(cursor, config, name, key, value)
    end
end

-- abstraction for "uci set" which handles corner cases
function utils.write_uci_option(cursor, config, name, key, value)
    -- ignore properties starting with .
    if utils.starts_with_dot(key) then
        return
    end
    -- avoid duplicate list settings
    if type(value) == 'table' then
        -- create set with unique values
        local set = {}
        -- read existing value
        local current = cursor:get(config, name, key)
        if type(current) == 'table' then
            set = utils.add_values_to_set(set, current)
        end
        set = utils.add_values_to_set(set, value)
        -- reset value var with set contents
        value = {}
        for item_value, present in pairs(set) do
            table.insert(value, item_value)
        end
    end
    cursor:set(config, name, key, value)
end

-- returns true if uci section is empty
function utils.is_uci_empty(table)
    for key, value in pairs(table) do
        if not utils.starts_with_dot(key) then return false end
    end
    return true
end

-- removes uci options
-- and removes section if empty
-- this is the inverse operation of `write_uci_section`
function utils.remove_uci_options(cursor, config, section)
    local name = section['.name']
    -- loop over keys in section and
    -- remove each one from cursor
    for key, value in pairs(section) do
        if not utils.starts_with_dot(key) then
            cursor:delete(config, name, key)
        end
    end
    -- remove entire section if empty
    local uci = cursor:get_all(config, name)
    if uci and utils.is_uci_empty(uci) then
        cursor:delete(config, name)
    end
end

-- returns true if a table is empty
function utils.is_table_empty(t)
    if next(t) == nil then
        return true
    else
        return false
    end
end

-- Code by David Kastrup
-- http://lua-users.org/wiki/DirTreeIterator
function utils.dirtree(dirParam)
    assert(dirParam and dirParam ~= '', 'directory parameter is missing or empty')
    if string.sub(dirParam, -1) == '/' then
        dirParam = string.sub(dirParam, 1, -2)
    end
    local function yieldtree(dir)
        for entry in lfs.dir(dir) do
            if entry ~= '.' and entry ~= '..' then
                entry = dir .. '/' ..entry
                local attr = lfs.attributes(entry)
                coroutine.yield(entry,attr)
                if attr.mode == 'directory' then
                    yieldtree(entry)
                end
            end
        end
    end
    return coroutine.wrap(function() yieldtree(dirParam) end)
end

function utils.file_exists(path)
    local f = io.open(path, 'r')
    if f ~= nil then io.close(f) return true end
    return false
end

function utils.file_to_set(path)
    local f = io.open(path, 'r')
    local set = {}
    for line in f:lines() do
        set[line] = true
    end
    return set
end

function utils.set_to_file(set, path)
    local f = io.open(path, 'w')
    for file, bool in pairs(set) do
        f:write(file, '\n')
    end
    io.close(f)
    return true
end

function utils.starts_with(str, start)
   return str:sub(1, #start) == start
end

-- iterates over a table in alphabeticaly order
function utils.sorted_pairs(t)
    -- collect keys
    local keys = {}
    for key in pairs(t) do keys[#keys+1] = key end

    table.sort(keys)

    -- return the iterator function
    local i = 0
    return function()
        i = i + 1
        if keys[i] then
            return keys[i], t[keys[i]]
        end
    end
end

return utils
